﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

using RingControls.Extensions;
using System.Diagnostics;

namespace RingControls
{
    public class Ring : ContentControl
    {
        #region Index
        public static DependencyProperty IndexProperty = DependencyProperty.Register(
            "Index",
            typeof(int),
            typeof(Ring),
            new PropertyMetadata(0, new PropertyChangedCallback(OnIndexChanged)));

        public int Index
        {
            get { return (int)GetValue(IndexProperty); }
            set
            {
                SetValue(IndexProperty, value);
                SafeRaiseEvent(IndexChanged, this, new IndexChangedEventArgs()
                {
                    Index = Index,
                });
            }
        }

        static void OnIndexChanged(DependencyObject property, DependencyPropertyChangedEventArgs args)
        {
            OnIndexChanged(property as Ring);
        }
        static void OnIndexChanged(Ring ring)
        {
            OnUpdateUI(ring);
        }
        #endregion

        #region Max
        public static DependencyProperty MaxProperty = DependencyProperty.Register(
            "Max",
            typeof(int),
            typeof(Ring),
            new PropertyMetadata(1));

        public int Max
        {
            get { return (int)GetValue(MaxProperty); }
            set { SetValue(MaxProperty, value); }
        }
        #endregion

        #region Cycle
        public static DependencyProperty CycleProperty = DependencyProperty.Register(
            "Cycle",
            typeof(int),
            typeof(Ring),
            new PropertyMetadata(1));

        public int Cycle
        {
            get { return (int)GetValue(CycleProperty); }
            set { SetValue(CycleProperty, value); }
        }
        #endregion

        #region Foreground
        public static new DependencyProperty ForegroundProperty = DependencyProperty.Register(
            "Foreground",
            typeof(Brush),
            typeof(Ring),
            new PropertyMetadata(new SolidColorBrush(Color.FromArgb(255, 255, 255, 255)), OnForegroundChanged));

        public new Brush Foreground
        {
            get { return (Brush)GetValue(ForegroundProperty); }
            set { SetValue(ForegroundProperty, value); }
        }
        static void OnForegroundChanged(DependencyObject property, DependencyPropertyChangedEventArgs args)
        {
            OnUpdateUI(property as Ring);
        }
        #endregion

        #region Highlight
        public static DependencyProperty HighlightProperty = DependencyProperty.Register(
            "Highlight",
            typeof(Brush),
            typeof(Ring),
            new PropertyMetadata(new SolidColorBrush(Color.FromArgb(255, 255, 255, 255)), OnHighlightChanged));

        public Brush Highlight
        {
            get { return (Brush)GetValue(HighlightProperty); }
            set { SetValue(HighlightProperty, value); }
        }
        static void OnHighlightChanged(DependencyObject property, DependencyPropertyChangedEventArgs args)
        {
            OnUpdateUI(property as Ring);
        }
        #endregion

        #region RingOpacity
        public static DependencyProperty RingOpacityProperty = DependencyProperty.Register(
            "RingOpacity",
            typeof(double),
            typeof(Ring),
            new PropertyMetadata(1.0));

        public double RingOpacity
        {
            get { return (double)GetValue(RingOpacityProperty); }
            set { SetValue(RingOpacityProperty, value); }
        }
        #endregion

        #region Radius
        public static DependencyProperty RadiusProperty = DependencyProperty.Register(
            "Radius",
            typeof(double),
            typeof(Ring),
            new PropertyMetadata(0.0, OnRadiusChanged));

        public double Radius
        {
            get { return (double)GetValue(RadiusProperty); }
            set { SetValue(RadiusProperty, value); }
        }
        static void OnRadiusChanged(DependencyObject property, DependencyPropertyChangedEventArgs args)
        {
            var ring = property as Ring;
            if (ring != null)
            {
                double radius = (double)args.NewValue;
                ring.Width = radius * 2;
                ring.Height = radius * 2;
            }
            OnUpdateUI(ring);
        }
        #endregion

        #region Thickness
        public static DependencyProperty ThicknessProperty = DependencyProperty.Register(
            "Thickness",
            typeof(double),
            typeof(Ring),
            new PropertyMetadata(0.0, OnThicknessChanged));

        public double Thickness
        {
            get { return (double)GetValue(ThicknessProperty); }
            set { SetValue(ThicknessProperty, value); }
        }
        static void OnThicknessChanged(DependencyObject property, DependencyPropertyChangedEventArgs args)
        {
            OnUpdateUI(property as Ring);
        }
        #endregion

        #region IsEnabledTouchWheel
        public static DependencyProperty IsEnabledTouchWheelProperty = DependencyProperty.Register(
            "IsEnabledTouchWheel",
            typeof(Boolean),
            typeof(Ring),
            new PropertyMetadata(false)
            );

        public bool IsEnabledTouchWheel
        {
            get { return (bool)GetValue(IsEnabledTouchWheelProperty); }
            set { SetValue(IsEnabledTouchWheelProperty, value); }
        }
        #endregion

        static void OnUpdateUI(Ring ring)
        {
            if (ring != null)
            {
                ring.DrawFigure(0, ring.Index);
            }
        }




        #region EventHandler
        public event EventHandler<IndexChangedEventArgs> IndexChanged;

        public event EventHandler<RoutedEventArgs> TouchWheelStart;

        public event EventHandler<RoutedEventArgs> TouchWheelEnd;
        #endregion


        #region Property
        public int OneCycle
        {
            get { return Max / Cycle; }
        }

        public int CurrentCycle
        {
            get
            {
                return Index == 0 ? 0 : (Index - 1) / OneCycle;
            }
        }

        // view state
        public bool IsTouchWheel { get; set; }
        #endregion


        #region Field
        // view elements
        private ContentPresenter _contentContainer;
        private Path _highlight;

        // draw support
        private RingFigure _highlightFigure = new RingFigure();
        #endregion



        public Ring()
        {
            this.DefaultStyleKey = typeof(Ring);

            this.ManipulationStarted += Ring_ManipulationStarted;
            this.ManipulationDelta += Ring_ManipulationDelta;
            this.ManipulationCompleted += Ring_ManipulationCompleted;
        }

        #region Event
        void Ring_ManipulationStarted(object sender, ManipulationStartedEventArgs e)
        {
            if (IsEnabledTouchWheel)
            {
                if (BeginTouchWheel())
                {
                    SafeRaiseEvent(TouchWheelStart, this, e);
                    e.Handled = true;
                }
            }
        }

        void Ring_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            if (IsEnabledTouchWheel)
            {
                if (ContinueTouchWheel(e.ManipulationOrigin))
                {
                    e.Handled = true;
                }
            }
        }

        void Ring_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
        {
            if (IsEnabledTouchWheel)
            {
                if (EndTouchWheel())
                {
                    SafeRaiseEvent(TouchWheelEnd, this, e);
                    e.Handled = true;
                }
            }
        }

        private bool BeginTouchWheel()
        {
            IsTouchWheel = true;
            this.CaptureMouse();

            return true;
        }

        private bool ContinueTouchWheel(Point origin)
        {
            if (IsTouchWheel)
            {
                var center = GetCenter();

                var position = origin;
                var vectorX = position.X - center.X;
                var vectorY = position.Y - center.Y;
                var radian = Math.Atan2(-vectorY, vectorX);

                var degree = radian.ToDegrees().DegreeToScreen().NormalizeDegree();
#if DEBUG
                Debug.WriteLine("degree: {0}", degree);
#endif
                var index = DegreeToNumber(degree);
#if DEBUG
                Debug.WriteLine("index: {0}", index);
#endif
                // update Index
                Index = NextIndex(RealIndex(index));

                return true;
            }
            else
            {
                return false;
            }
        }

        private bool EndTouchWheel()
        {
            if (IsTouchWheel)
            {
                this.ReleaseMouseCapture();
                IsTouchWheel = false;

                return true;
            }
            else
            {
                return false;
            }
        }
        #endregion


        #region Override
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            // find child elements
            _contentContainer = (ContentPresenter)this.GetTemplateChild("ContentContainer");
            _highlight = (Path)this.GetTemplateChild("Highlight");

            // create highlight objects
            _highlightFigure.ApplyTo(_highlight);
        }
        #endregion


        #region Draw
        private void DrawFigure(int start, int end, double innerR, double outerR)
        {
            if (_highlight == null)
                return;

            if (start < 0 || end < 0)
            {
                throw new ArgumentException("start or end needs over 0.");
            }

            if (start == 0 && end == 0)
            {
                _highlightFigure.Hide();
            }
            else
            {
                var center = GetCenter();
                var startRadian = NumberToRadian(start).RadianToScreen().NormalizeRadian();
                var endRadian = NumberToRadian(end).RadianToScreen().NormalizeRadian();

                _highlightFigure.Show();
                _highlightFigure.Draw(startRadian, endRadian, center, outerR, innerR, true);
            }
        }


        private void DrawFigure(int start, int end)
        {
            DrawFigure(start, end, Radius - Thickness, Radius);
        }
        #endregion


        #region RaiseEvent
        public static void SafeRaiseEvent<T>(EventHandler<T> eventToRaise, object sender, T args) where T : EventArgs
        {
            if (eventToRaise != null)
            {
                eventToRaise(sender, args);
            }
        }
        #endregion

        private Point GetCenter()
        {
            double top = (double)GetValue(Canvas.TopProperty);
            double left = (double)GetValue(Canvas.LeftProperty);
            return new Point(left + Radius, top + Radius);
        }

        private int RealIndex(int index)
        {
            return index + (OneCycle * CurrentCycle);
        }

        private int NextIndex(int index)
        {
            int diff = Index - index;
            if (OneCycle / 2 < Math.Abs(diff))
            {
                if (0 < diff)
                {
#if DEBUG
                    Debug.WriteLine("next cycle");
#endif
                    // next cycle
                    index = (index + OneCycle);
                }
                else
                {
#if DEBUG
                    Debug.WriteLine("prev cycle");
#endif
                    // prev cycle
                    index = (index - OneCycle + Max);
                }
            }
            else
            {
                // same cycle
            }

            return index % Max + 1;
        }
        private double RealDegree(double degree)
        {
            return degree + 360 * CurrentCycle;
        }

        private int DegreeToNumber(double degree)
        {
            double angle = 360.0 / OneCycle;
            return (int)((degree) / angle);
        }

        private double NumberToDegree(int number)
        {
            double angle = 360.0 / OneCycle;
            return angle * number;

        }

        private double NumberToRadian(int number)
        {
            return NumberToDegree(number).ToRadians();
        }

    }
}
